package org.apache.rocketmq.client.java.retry;

import apache.rocketmq.v2.ExponentialBackoff;
import com.google.common.base.MoreObjects;
import com.google.protobuf.util.Durations;

import java.time.Duration;

import static apache.rocketmq.v2.RetryPolicy.StrategyCase.EXPONENTIAL_BACKOFF;
import static com.google.common.base.Preconditions.checkArgument;

/**
 * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
 * @author：陈清风扬，个人微信号：chenqingfengyangjj。
 * @date:2025/6/10
 * @方法描述：客户端发送请求失败之后的重试策略，每次请求失败重新发送请求时，等待的时间会按照指数递增，直到达到最大退避时间，也就是最大的等待时间
 * 到达最大的等待时间后，这个时间就不再会增加了。这么做的原因是，如果连续失败的请求，每次重试间隔时间太短，可能会导致服务端无法处理这些请求，从而导致服务端压力过大，甚至崩溃
 * 其实就是为了减轻系统压力的
 */
public class ExponentialBackoffRetryPolicy implements RetryPolicy {

    //失败重试次数
    private final int maxAttempts;

    //初始退避时间，也就是请求第一次重试时要等待的时间
    private final Duration initialBackoff;

    //最大退避时间
    private final Duration maxBackoff;

    //退避指数，也就是每次重试间隔时间会乘以这个数
    private final double backoffMultiplier;

    //构造方法
    public ExponentialBackoffRetryPolicy(int maxAttempts, Duration initialBackoff, Duration maxBackoff,
                                         double backoffMultiplier) {
        this.maxAttempts = maxAttempts;
        this.initialBackoff = initialBackoff;
        this.maxBackoff = maxBackoff;
        this.backoffMultiplier = backoffMultiplier;
    }

    //创建一个立即重试策略对象的方法，之所以是立即充实策略，是因为重试策略的initialBackoff和maxBackoff都为Duration.ZERO
    //退避指数为1
    public static ExponentialBackoffRetryPolicy immediatelyRetryPolicy(int maxAttempts) {
        return new ExponentialBackoffRetryPolicy(maxAttempts, Duration.ZERO, Duration.ZERO, 1);
    }


    @Override
    public int getMaxAttempts() {
        return maxAttempts;
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：计算下一次请求重试要等待的时间的方法，参数attempt表示当前尝试的次数
     */
    @Override
    public Duration getNextAttemptDelay(int attempt) {
        checkArgument(attempt > 0, "attempt must be positive");
        double delayNanos = Math.min(initialBackoff.toNanos() * Math.pow(backoffMultiplier,
                1.0 * (attempt - 1)), maxBackoff.toNanos());
        if (delayNanos <= 0) {
            return Duration.ZERO;
        }
        return Duration.ofNanos((long) delayNanos);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("maxAttempts", maxAttempts)
                .add("initialBackoff", initialBackoff)
                .add("maxBackoff", maxBackoff)
                .add("backoffMultiplier", backoffMultiplier)
                .toString();
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：将ProtocolBuffer格式的RetryPolicy对象转换为ExponentialBackoffRetryPolicy实例的方法
     */
    public static ExponentialBackoffRetryPolicy fromProtobuf(apache.rocketmq.v2.RetryPolicy retryPolicy) {
        //判断重试策略是否为指数退避策略
        if (!EXPONENTIAL_BACKOFF.equals(retryPolicy.getStrategyCase())) {
            throw new IllegalArgumentException();
        }
        final ExponentialBackoff exponentialBackoff = retryPolicy.getExponentialBackoff();
        return new ExponentialBackoffRetryPolicy(retryPolicy.getMaxAttempts(),
                Duration.ofNanos(Durations.toNanos(exponentialBackoff.getInitial())),
                Duration.ofNanos(Durations.toNanos(exponentialBackoff.getMax())),
                exponentialBackoff.getMultiplier());
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：更新客户端的重试策略的方法
     */
    @Override
    public RetryPolicy updateBackoff(apache.rocketmq.v2.RetryPolicy retryPolicy) {
        if (!EXPONENTIAL_BACKOFF.equals(retryPolicy.getStrategyCase())) {
            throw new IllegalArgumentException("strategy must be exponential backoff");
        }
        return updateBackoff(retryPolicy.getExponentialBackoff());
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：更新客户端的重试策略的方法，该方法会在ProducerSettings对象的applySettingsCommand()方法中调用，该方法一旦被调用
     * 客户端的重试策略就会被更新了
     */
    private RetryPolicy updateBackoff(ExponentialBackoff backoff) {
        return new ExponentialBackoffRetryPolicy(maxAttempts,
                Duration.ofNanos(Durations.toNanos(backoff.getInitial())),
                Duration.ofNanos(Durations.toNanos(backoff.getMax())),
                backoff.getMultiplier());
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：将客户端的重试策略转换为ProtocolBuffer格式的RetryPolicy对象的方法
     */
    @Override
    public apache.rocketmq.v2.RetryPolicy toProtobuf() {
        ExponentialBackoff exponentialBackoff = ExponentialBackoff.newBuilder()
                .setMultiplier((float) backoffMultiplier)
                .setMax(Durations.fromNanos(maxBackoff.toNanos()))
                .setInitial(Durations.fromNanos(initialBackoff.toNanos()))
                .build();
        return apache.rocketmq.v2.RetryPolicy.newBuilder()
                .setMaxAttempts(maxAttempts)
                .setExponentialBackoff(exponentialBackoff)
                .build();
    }
}
